/**
 * Created by msi on 2017/11/28 0028.
 */
/*
 FDEB algorithm implementation [www.win.tue.nl/~dholten/papers/forcebundles_eurovis.pdf].
 Author: Corneliu S. (github.com/upphiminn)
 2013
 */
import * as d3 from "d3";
import React from "react";
import {Card, Col, Row, Slider, Upload, message, Button, Icon} from "antd";
var FileSaver = require('file-saver');
//添加d3-ForceEdgeBundling
(function () {
    d3.ForceEdgeBundling = function () {
        var data_nodes = {}, // {'nodeid':{'x':,'y':},..}
            data_edges = [], // [{'source':'nodeid1', 'target':'nodeid2'},..]
            compatibility_list_for_edge = [],
            subdivision_points_for_edge = [],
            K = 0.1, // global bundling constant controlling edge stiffness
            S_initial = 0.001, // init. distance to move points 移动点的距离
            P_initial = 1, // init. subdivision number 细分的数目
            P_rate = 2, // subdivision rate increase 细分的增加率
            C = 6, // number of cycles to perform 执行的周期数目
            I_initial = 90, // init. number of iterations for cycle  迭代次数会影响曲线度
            I_rate = 0.6666667, // rate at which iteration number decreases i.e. 2/3
            compatibility_threshold = 0.6,
            eps = 1e-6;


        /*** Geometry Helper Methods ***/
        function vector_dot_product(p, q) {
            return p.x * q.x + p.y * q.y;
        }

        function edge_as_vector(P) {
            return {
                'x': data_nodes[P.target].x - data_nodes[P.source].x,
                'y': data_nodes[P.target].y - data_nodes[P.source].y
            }
        }

        function edge_length(e) {
            // handling nodes that are on the same location, so that K/edge_length != Inf
            if (Math.abs(data_nodes[e.source].x - data_nodes[e.target].x) < eps &&
                Math.abs(data_nodes[e.source].y - data_nodes[e.target].y) < eps) {
                return eps;
            }

            return Math.sqrt(Math.pow(data_nodes[e.source].x - data_nodes[e.target].x, 2) +
                Math.pow(data_nodes[e.source].y - data_nodes[e.target].y, 2));
        }

        function custom_edge_length(e) {
            return Math.sqrt(Math.pow(e.source.x - e.target.x, 2) + Math.pow(e.source.y - e.target.y, 2));
        }

        function edge_midpoint(e) {
            var middle_x = (data_nodes[e.source].x + data_nodes[e.target].x) / 2.0;
            var middle_y = (data_nodes[e.source].y + data_nodes[e.target].y) / 2.0;

            return {
                'x': middle_x,
                'y': middle_y
            };
        }

        function compute_divided_edge_length(e_idx) {
            var length = 0;

            for (var i = 1; i < subdivision_points_for_edge[e_idx].length; i++) {
                var segment_length = euclidean_distance(subdivision_points_for_edge[e_idx][i], subdivision_points_for_edge[e_idx][i - 1]);
                length += segment_length;
            }

            return length;
        }

        function euclidean_distance(p, q) {
            return Math.sqrt(Math.pow(p.x - q.x, 2) + Math.pow(p.y - q.y, 2));
        }

        function project_point_on_line(p, Q) {
            var L = Math.sqrt((Q.target.x - Q.source.x) * (Q.target.x - Q.source.x) + (Q.target.y - Q.source.y) * (Q.target.y - Q.source.y));
            var r = ((Q.source.y - p.y) * (Q.source.y - Q.target.y) - (Q.source.x - p.x) * (Q.target.x - Q.source.x)) / (L * L);

            return {
                'x': (Q.source.x + r * (Q.target.x - Q.source.x)),
                'y': (Q.source.y + r * (Q.target.y - Q.source.y))
            };
        }

        /*** ********************** ***/

        /*** Initialization Methods ***/
        function initialize_edge_subdivisions() {
            for (var i = 0; i < data_edges.length; i++) {
                if (P_initial === 1) {
                    subdivision_points_for_edge[i] = []; //0 subdivisions
                } else {
                    subdivision_points_for_edge[i] = [];
                    subdivision_points_for_edge[i].push(data_nodes[data_edges[i].source]);
                    subdivision_points_for_edge[i].push(data_nodes[data_edges[i].target]);
                    subdivision_points_for_edge[i].push(data_nodes[data_edges[i].v]);
                }
            }
        }

        function initialize_compatibility_lists() {
            for (var i = 0; i < data_edges.length; i++) {
                compatibility_list_for_edge[i] = []; //0 compatible edges.
            }
        }

        function filter_self_loops(edgelist) {
            var filtered_edge_list = [];

            for (var e = 0; e < edgelist.length; e++) {
                if (data_nodes[edgelist[e].source].x != data_nodes[edgelist[e].target].x ||
                    data_nodes[edgelist[e].source].y != data_nodes[edgelist[e].target].y) { //or smaller than eps
                    filtered_edge_list.push(edgelist[e]);
                }
            }

            return filtered_edge_list;
        }

        /*** ********************** ***/

        /*** Force Calculation Methods ***/
        function apply_spring_force(e_idx, i, kP) {
            var prev = subdivision_points_for_edge[e_idx][i - 1];
            var next = subdivision_points_for_edge[e_idx][i + 1];
            var crnt = subdivision_points_for_edge[e_idx][i];
            var x = prev.x - crnt.x + next.x - crnt.x;
            var y = prev.y - crnt.y + next.y - crnt.y;

            x *= kP;
            y *= kP;

            return {
                'x': x,
                'y': y
            };
        }

        function apply_electrostatic_force(e_idx, i) {
            var sum_of_forces = {
                'x': 0,
                'y': 0
            };
            var compatible_edges_list = compatibility_list_for_edge[e_idx];

            for (var oe = 0; oe < compatible_edges_list.length; oe++) {
                var force = {
                    'x': subdivision_points_for_edge[compatible_edges_list[oe]][i].x - subdivision_points_for_edge[e_idx][i].x,
                    'y': subdivision_points_for_edge[compatible_edges_list[oe]][i].y - subdivision_points_for_edge[e_idx][i].y
                };

                if ((Math.abs(force.x) > eps) || (Math.abs(force.y) > eps)) {
                    var diff = (1 / Math.pow(custom_edge_length({
                        'source': subdivision_points_for_edge[compatible_edges_list[oe]][i],
                        'target': subdivision_points_for_edge[e_idx][i]
                    }), 1));

                    sum_of_forces.x += force.x * diff;
                    sum_of_forces.y += force.y * diff;
                }
            }

            return sum_of_forces;
        }


        function apply_resulting_forces_on_subdivision_points(e_idx, P, S) {
            var kP = K / (edge_length(data_edges[e_idx]) * (P + 1)); // kP=K/|P|(number of segments), where |P| is the initial length of edge P.
            // (length * (num of sub division pts - 1))
            var resulting_forces_for_subdivision_points = [{
                'x': 0,
                'y': 0,
                'v': 0
            }];

            for (var i = 1; i < P + 1; i++) { // exclude initial end points of the edge 0 and P+1
                var resulting_force = {
                    'x': 0,
                    'y': 0,
                    'v': 0
                };

                var spring_force = apply_spring_force(e_idx, i, kP);
                var electrostatic_force = apply_electrostatic_force(e_idx, i, S);

                resulting_force.x = S * (spring_force.x + electrostatic_force.x);
                resulting_force.y = S * (spring_force.y + electrostatic_force.y);
                resulting_force.v = data_edges[e_idx].v;
                resulting_forces_for_subdivision_points.push(resulting_force);
            }

            resulting_forces_for_subdivision_points.push({
                'x': 0,
                'y': 0,
                'v': 0
            });

            return resulting_forces_for_subdivision_points;
        }

        /*** ********************** ***/

        /*** Edge Division Calculation Methods ***/
        function update_edge_divisions(P) {
            for (var e_idx = 0; e_idx < data_edges.length; e_idx++) {
                if (P === 1) {
                    subdivision_points_for_edge[e_idx].push(data_nodes[data_edges[e_idx].source]); // source
                    subdivision_points_for_edge[e_idx].push(edge_midpoint(data_edges[e_idx])); // mid point
                    subdivision_points_for_edge[e_idx].push(data_nodes[data_edges[e_idx].target]); // target
                } else {
                    var divided_edge_length = compute_divided_edge_length(e_idx);
                    var segment_length = divided_edge_length / (P + 1);
                    var current_segment_length = segment_length;
                    var new_subdivision_points = [];
                    new_subdivision_points.push(data_nodes[data_edges[e_idx].source]); //source

                    for (var i = 1; i < subdivision_points_for_edge[e_idx].length; i++) {
                        var old_segment_length = euclidean_distance(subdivision_points_for_edge[e_idx][i], subdivision_points_for_edge[e_idx][i - 1]);

                        while (old_segment_length > current_segment_length) {
                            var percent_position = current_segment_length / old_segment_length;
                            var new_subdivision_point_x = subdivision_points_for_edge[e_idx][i - 1].x;
                            var new_subdivision_point_y = subdivision_points_for_edge[e_idx][i - 1].y;

                            new_subdivision_point_x += percent_position * (subdivision_points_for_edge[e_idx][i].x - subdivision_points_for_edge[e_idx][i - 1].x);
                            new_subdivision_point_y += percent_position * (subdivision_points_for_edge[e_idx][i].y - subdivision_points_for_edge[e_idx][i - 1].y);
                            new_subdivision_points.push({
                                'x': new_subdivision_point_x,
                                'y': new_subdivision_point_y,
                                'v': data_edges[e_idx].v
                            });

                            old_segment_length -= current_segment_length;
                            current_segment_length = segment_length;
                        }
                        current_segment_length -= old_segment_length;
                    }
                    new_subdivision_points.push(data_nodes[data_edges[e_idx].target]); //target
                    subdivision_points_for_edge[e_idx] = new_subdivision_points;
                }
            }
        }

        /*** ********************** ***/

        /*** Edge compatibility measures ***/
        function angle_compatibility(P, Q) {
            return Math.abs(vector_dot_product(edge_as_vector(P), edge_as_vector(Q)) / (edge_length(P) * edge_length(Q)));
        }

        function scale_compatibility(P, Q) {
            var lavg = (edge_length(P) + edge_length(Q)) / 2.0;
            return 2.0 / (lavg / Math.min(edge_length(P), edge_length(Q)) + Math.max(edge_length(P), edge_length(Q)) / lavg);
        }

        function position_compatibility(P, Q) {
            var lavg = (edge_length(P) + edge_length(Q)) / 2.0;
            var midP = {
                'x': (data_nodes[P.source].x + data_nodes[P.target].x) / 2.0,
                'y': (data_nodes[P.source].y + data_nodes[P.target].y) / 2.0
            };
            var midQ = {
                'x': (data_nodes[Q.source].x + data_nodes[Q.target].x) / 2.0,
                'y': (data_nodes[Q.source].y + data_nodes[Q.target].y) / 2.0
            };

            return lavg / (lavg + euclidean_distance(midP, midQ));
        }

        function edge_visibility(P, Q) {
            var I0 = project_point_on_line(data_nodes[Q.source], {
                'source': data_nodes[P.source],
                'target': data_nodes[P.target]
            });
            var I1 = project_point_on_line(data_nodes[Q.target], {
                'source': data_nodes[P.source],
                'target': data_nodes[P.target]
            }); //send actual edge points positions
            var midI = {
                'x': (I0.x + I1.x) / 2.0,
                'y': (I0.y + I1.y) / 2.0
            };
            var midP = {
                'x': (data_nodes[P.source].x + data_nodes[P.target].x) / 2.0,
                'y': (data_nodes[P.source].y + data_nodes[P.target].y) / 2.0
            };

            return Math.max(0, 1 - 2 * euclidean_distance(midP, midI) / euclidean_distance(I0, I1));
        }

        function visibility_compatibility(P, Q) {
            return Math.min(edge_visibility(P, Q), edge_visibility(Q, P));
        }

        function compatibility_score(P, Q) {
            return (angle_compatibility(P, Q) * scale_compatibility(P, Q) * position_compatibility(P, Q) * visibility_compatibility(P, Q));
        }

        function are_compatible(P, Q) {
            return (compatibility_score(P, Q) >= compatibility_threshold);
        }

        function compute_compatibility_lists() {
            for (var e = 0; e < data_edges.length - 1; e++) {
                for (var oe = e + 1; oe < data_edges.length; oe++) { // don't want any duplicates
                    if (are_compatible(data_edges[e], data_edges[oe])) {
                        compatibility_list_for_edge[e].push(oe);
                        compatibility_list_for_edge[oe].push(e);
                    }
                }
            }
        }

        /*** ************************ ***/

        /*** Main Bundling Loop Methods ***/
        var forcebundle = function () {
            var S = S_initial;
            var I = I_initial;
            var P = P_initial;

            initialize_edge_subdivisions();
            initialize_compatibility_lists();
            update_edge_divisions(P);
            compute_compatibility_lists();

            for (var cycle = 0; cycle < C; cycle++) {
                for (var iteration = 0; iteration < I; iteration++) {
                    var forces = [];
                    for (var edge = 0; edge < data_edges.length; edge++) {
                        forces[edge] = apply_resulting_forces_on_subdivision_points(edge, P, S);
                    }
                    for (var e = 0; e < data_edges.length; e++) {
                        for (var i = 0; i < P + 1; i++) {
                            subdivision_points_for_edge[e][i].x += forces[e][i].x;
                            subdivision_points_for_edge[e][i].y += forces[e][i].y;
                            subdivision_points_for_edge[e][i].v = forces[e][i].v;
                        }
                    }
                }
                // prepare for next cycle
                S = S / 2;
                P = P * P_rate;
                I = I_rate * I;

                update_edge_divisions(P);
                //console.log('C' + cycle);
                //console.log('P' + P);
                //console.log('S' + S);
            }
            return subdivision_points_for_edge;
        };
        /*** ************************ ***/


        /*** Getters/Setters Methods ***/
        forcebundle.nodes = function (nl) {
            if (arguments.length === 0) {
                return data_nodes;
            } else {
                data_nodes = nl;
            }

            return forcebundle;
        };

        forcebundle.edges = function (ll) {
            if (arguments.length === 0) {
                return data_edges;
            } else {
                data_edges = filter_self_loops(ll); //remove edges to from to the same point
            }

            return forcebundle;
        };

        forcebundle.bundling_stiffness = function (k) {
            if (arguments.length === 0) {
                return K;
            } else {
                K = k;
            }

            return forcebundle;
        };

        forcebundle.step_size = function (step) {
            if (arguments.length === 0) {
                return S_initial;
            } else {
                S_initial = step;
            }

            return forcebundle;
        };

        forcebundle.cycles = function (c) {
            if (arguments.length === 0) {
                return C;
            } else {
                C = c;
            }

            return forcebundle;
        };

        forcebundle.iterations = function (i) {
            if (arguments.length === 0) {
                return I_initial;
            } else {
                I_initial = i;
            }

            return forcebundle;
        };

        forcebundle.iterations_rate = function (i) {
            if (arguments.length === 0) {
                return I_rate;
            } else {
                I_rate = i;
            }

            return forcebundle;
        };

        forcebundle.subdivision_points_seed = function (p) {
            var P = P_initial;
            if (arguments.length == 0) {
                return P;
            } else {
                P = p;
            }

            return forcebundle;
        };

        forcebundle.subdivision_rate = function (r) {
            if (arguments.length === 0) {
                return P_rate;
            } else {
                P_rate = r;
            }

            return forcebundle;
        };

        forcebundle.compatibility_threshold = function (t) {
            if (arguments.length === 0) {
                return compatibility_threshold;
            } else {
                compatibility_threshold = t;
            }

            return forcebundle;
        };

        /*** ************************ ***/

        return forcebundle;
    }
})();

var svg, baseMap

function showForceEdgeBundlingDrawSVG(results, nnodes) {

    svg = d3.select("#svg").append("svg")
        .attr("width", 1200)
        .attr("height", 600);

    svg = svg.append('g');
    svg.append('rect').attr({
        'fill': '#111155',
        'width': 1200,
        'height': 600
    });
    svg.attr('transform', 'translate(20, 20)');

    d3.json("jiedao_polygon.geojson", function (error, data) {
        var centers = getCenters(data.features);
        var zoomScale = getZoomScale(data.features, 1200, 600);

        var scalev = d3.scale.linear().domain([minv, maxv]).range([0, 0.05]);
        //先定义一个投影
        projection = d3.geo.mercator()
            .center(centers)
            .translate([1200 / 2, 600 / 2])
            .scale(zoomScale * 30);

        //通过投影函数生成地理路径生成器
        path = d3.geo.path().projection(projection);

        baseMap = svg.append("g")
            .attr(function (d) {
                d.dx = 110;
                d.dy = 40;
            })


        var city = baseMap.selectAll("path")
            .data(data.features) //数据
            .enter()
            .append("path").style('stroke', 'white')
            .attr("fill", function (d, i) {
                return "#111155";
            }).attr("d", path)

            .on("mouseover", function (d, i) {
                d3.select(this).attr("fill", "#ccc");
            })
            .on("mouseout", function (d, i) {
                d3.select(this).attr("fill", "#111155");
            });

        var max = d3.max(d3.entries(nnodes), function (d) {
            return d.value.c
        })

        var min = d3.min(d3.entries(nnodes), function (d) {
            return d.value.c
        })


        var linear = d3.scale.linear().domain([min, max]).range([1, 5])
        //draw nodes 画节点的时候画半径 用大小表示进出流量大小
        svg.selectAll('.node')
            .data(d3.entries(nnodes))
            .enter()
            .append('circle')
            .classed('node', true)
            .attr('r', function (d) {
                return linear(d.value.c)
            })
            .attr('fill', '#ffee00')
            .attr('cx', function (d) {
                return projection([d.value.x, d.value.y])[0];
            })
            .attr('cy', function (d) {
                return projection([d.value.x, d.value.y])[1];
            });


        var d3line = d3.svg.line()
            .x(function (d) {
                // return d.x;
                return projection([d.x, d.y])[0];
            })
            .y(function (d) {
                // return d.y;
                return projection([d.x, d.y])[1]
            })
        //plot the data
        for (var i = 0; i < results.length; i++) {
            debugger
            if (results[i][2].v) {
                svg.append("path").attr("d", d3line(results[i]))
                    .style("stroke-width", 1)
                    .style("stroke", "#ff2222")
                    .style("fill", "none")
                    .style('stroke-opacity', scalev(results[i][2].v));
            } else {
                svg.append("path").attr("d", d3line(results[i]))
                    .style("stroke-width", 1)
                    .style("stroke", "#ff2222")
                    .style("fill", "none")
                    .style('stroke-opacity', 0.05);
            }
        }
    });


}

var projection, path;
//-------------------------------------------------------------
//获得地图的中心
function getCenters(features) {
    var longitudeMin = 100000;
    var latitudeMin = 100000;
    var longitudeMax = 0;
    var latitudeMax = 0;
    features.forEach(function (e) {
        var a = d3.geo.bounds(e);
        if (a[0][0] < longitudeMin) {
            longitudeMin = a[0][0];
        }
        if (a[0][1] < latitudeMin) {
            latitudeMin = a[0][1];
        }
        if (a[1][0] > longitudeMax) {
            longitudeMax = a[1][0];
        }
        if (a[1][1] > latitudeMax) {
            latitudeMax = a[1][1];
        }
    });

    var a = (longitudeMax + longitudeMin) / 2;
    var b = (latitudeMax + latitudeMin) / 2;

    return [a, b];
}

//设置地图的大小
function getZoomScale(features, width, height) {
    var longitudeMin = 100000;
    var latitudeMin = 100000;
    var longitudeMax = 0;
    var latitudeMax = 0;
    features.forEach(function (e) {
        var a = d3.geo.bounds(e);

        if (a[0][0] < longitudeMin) {
            longitudeMin = a[0][0];
        }
        if (a[0][1] < latitudeMin) {
            latitudeMin = a[0][1];
        }
        if (a[1][0] > longitudeMax) {
            longitudeMax = a[1][0];
        }
        if (a[1][1] > latitudeMax) {
            latitudeMax = a[1][1];
        }
    });

    var a = longitudeMax - longitudeMin;
    var b = latitudeMax - latitudeMin;

    return Math.min(width / a, height / b);
}


var maxv = 0;
var minv = Number.MAX_VALUE;
function showForceEdgeBundling(value) {


    d3.json("ODLine.json", function (odline) {

        var eedges = [];
        var nnodes = {};//注意node要是id为属性
        var min_x = Number.MAX_VALUE;
        var max_x = 0;
        var min_y = Number.MAX_VALUE;
        var max_y = 0;


        // 先进行统计计算
        odline.nodes.forEach(function (node, i) {
            //node.index = i;
            node.count = 0;
        });
        // 将边的值赋予到矩阵中，并计算节点出现的次数
        odline.links.forEach(function (link) {
            odline.nodes[link.o - 1].count += link.v;
            odline.nodes[link.d - 1].count += link.v;
        });


        //构造相应的数据

        // 先进行统计计算
        odline.nodes.forEach(function (node, i) {
            nnodes[i + 1] = { //注意索引和实际相差1
                'x': node.x,
                'y': node.y,
                'c': node.count
            };
            min_x = Math.min(min_x, node.x);
            max_x = Math.max(max_x, node.x);
            min_y = Math.min(min_y, node.y);
            max_y = Math.max(max_y, node.y);
        });


        odline.links.forEach(function (link, i) {
            eedges.push({
                'source': link.o,
                'target': link.d,
                'v': link.v
            })
            maxv = Math.max(maxv, link.v);
            minv = Math.min(minv, link.v);
        });

        //绘制曲线
        //Run the FDEB algorithm using default values on the data
        var fbundling = d3.ForceEdgeBundling().nodes(nnodes).edges(eedges).iterations(value);
        var results = fbundling();

        var sa = {
            "links": results,
            "nodes": nnodes
        };
        showForceEdgeBundlingDrawSVG(results, nnodes);
        var blob = new Blob([JSON.stringify(sa)], {type: "text/plain;charset=utf-8"});
        FileSaver.saveAs(blob, value + "_iterations.txt");
    });
}

class ODLine extends React.Component {
    componentDidMount() {
        function ProcessFile(e) {
            var file = document.getElementById('file').files[0];
            if (file) {
                var reader = new FileReader();
                reader.onload = function (event) {
                    var txt = event.target.result;
                    let json = JSON.parse(txt);
                    showForceEdgeBundlingDrawSVG(json.links, json.nodes);
                };
                reader.readAsText(file);
            }
        }

        function contentLoaded() {
            document.getElementById('file').addEventListener('change',
                ProcessFile, false);
        }

        window.addEventListener("DOMContentLoaded", contentLoaded, false);
    }

    state = {
        inputValue: 0,
    }
    onSliderChange = (value) => {
        this.setState({
            inputValue: parseInt(value),
            // inputValue: (value),
        });
    }

    onRunClick() {
        showForceEdgeBundling(this.state.inputValue);
    }

    render() {
        // var That = this;
        return (
            <div className="gutter-example">
                <Row gutter={100}>
                    <Col className="gutter-row" md={24}>
                        <div className="gutter-box">
                            <Card title="OD曲线图" bordered={false}>
                                <Row>
                                    <Col span={4}>
                                        <p>迭代次数（聚合线的弯曲度）：</p>
                                    </Col>
                                    <Col span={12}>
                                        <Slider min={0} max={90} step={1}
                                                onChange={this.onSliderChange}
                                                value={this.state.inputValue}/>
                                    </Col>
                                    <Button onClick={this.onRunClick.bind(this)}>运行</Button>
                                </Row>

                                <div>请选取一个保存文件: <input type="file" id="file" name="file"/></div>
                                <div id="svg">
                                </div>
                            </Card>
                        </div>
                    </Col>
                </Row>
            </div>
        )
    }
}

export default ODLine;